package com.kgajowy.objects;

import edu.pjwstk.jps.datastore.IOID;
import edu.pjwstk.jps.datastore.ISBAObject;
import edu.pjwstk.jps.datastore.ISBAStore;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

public class SBAStore implements ISBAStore {
    
    private int id = 0;
    private TreeMap<DBID, SBAObject> store;
    
    public SBAStore(){
        store = new TreeMap<DBID, SBAObject>();
        DBID nID = (DBID) generateUniqueOID();
        store.put(nID, new ComplexObject("root", (DBID)nID, null));
    }
    
    @Override
    public ISBAObject retrieve(IOID oid) {
        return store.get(oid);
    }
    
    public boolean hasElementAChild(IOID elem, IOID child) {
        boolean result = false;
        DBID lookingFor = (DBID) child;

        SBAObject parent = (SBAObject) retrieve(elem);
        if ( parent instanceof ComplexObject ){
            ComplexObject c = (ComplexObject) parent;
            for ( IOID innerI : c.getChildElements() ) {
                if ( (DBID)innerI == (DBID)child ){
                    result = true;
                    break;
                }
            }
        }
        return result;
    }

    @Override
    public IOID getEntryOID() {
        return store.firstKey();
    }

    @Override
    public void loadXML(String filePath) {
        try {
            File fXmlFile = new File(filePath);
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(fXmlFile);
            doc.getDocumentElement().normalize();

            walkNode(doc.getDocumentElement(), getEntryOID());
            
        } catch (Exception e) {e.printStackTrace();}
    }
    
    public TreeMap<DBID, SBAObject> getElements() {
        return this.store;
    }
    
    @SuppressWarnings({
            "unchecked", "rawtypes"
    })
    private void walkNode(Element e, IOID parentID){
        DBID nID = (DBID) generateUniqueOID();
        if ( e.getChildNodes().getLength() > 1 ) {
            //complex object
            String name = e.getNodeName();
            store.put(nID, new ComplexObject(name, nID, null));
            for (int temp = 0; temp < e.getChildNodes().getLength(); temp++) {
                Node nNode = e.getChildNodes().item(temp);
                if (nNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element eElement = (Element) nNode;
                    walkNode(eElement,nID);
                }
            }
        } else {
            //simple object
            String name = e.getNodeName();
            String value = e.getChildNodes().item(0).getNodeValue();
            Object o = getValueOfString(value);
            store.put(nID, new StringObject(name, (DBID) nID, value));
            // update parent now
            
        }
        if ( parentID != null ) {
            ((ComplexObject)retrieve(parentID)).add(nID);
        }

    }

    @Override
    public IOID generateUniqueOID() {
        int i = 0;
        synchronized (SBAStore.class) {
            i = this.id;
            this.id++;
        }
        return new DBID(i);
    }

    public static boolean isSimpleType(Object o){
        /*
        System.out.println("checking class of " + o.getClass());
        System.out.println("\twith " + String.class);
        System.out.println("\twith " + Integer.class);
        System.out.println("\twith " + Double.class);
        System.out.println("\twith " + Boolean.class);
       */ 
        if ( o.getClass() == Integer.class ||
                o.getClass() == int.class ||
                o.getClass() == String.class ||
                o.getClass() == Double.class ||
                o.getClass() == double.class ||
                o.getClass() == Boolean.class || 
                o.getClass() == boolean.class){
            return true;
        } else {
            return false;
        }
    }
    
    public static Object getValueOfString(String s){
        Scanner sc = new Scanner(s);
        return
            sc.hasNextInt() ? sc.nextInt() :
            sc.hasNextLong() ? sc.nextLong() :
            sc.hasNextDouble() ? sc.nextDouble() :
            sc.hasNext() ? sc.next() :
            s;
    }
 
    @Override
    public void addJavaObject(Object o, String objectName) {
        Class c = o.getClass();
        DBID newId = (DBID)generateUniqueOID();
        if ( c == Integer.class || c == int.class) {
            store.put(
                    newId, new IntegerObject(objectName,newId,(Integer) o)
            );
        }
        else if ( c == String.class ) {
            store.put(
                    newId, new StringObject(objectName,newId,(String) o)
            );
        }
        else if ( c == Double.class || c == double.class ) {
            store.put(
                    newId, new DoubleObject(objectName,newId,(Double) o)
            );
        }
        else if ( c == Boolean.class || c == boolean.class ) {
            store.put(
                    newId, new BooleanObject(objectName,newId,(Boolean) o)
            );
        } else {

           //System.out.println(objectName + " is not a very simple object: " + c);
            for ( Field f : o.getClass().getFields() ) {
                //System.out.println("\t" + objectName + " has field: " + f.getType());
                try {
                        if ( isSimpleType(f.get(o)) ) {
                                addJavaObject(f.get(o), f.getName());
                                //System.out.println(" \t it is");
                        } else {
                            //System.out.println(" \t it is not");
                            if (Collection.class.isAssignableFrom(f.getType())) {
                                LinkedList<Object> list = (LinkedList) f.get(o);
                                addJavaCollection(list, f.getName());
                            }
                        }
                } catch (IllegalArgumentException e) {e.printStackTrace();
                } catch (IllegalAccessException e) {e.printStackTrace();}
                catch (SecurityException e) {e.printStackTrace();} 
            }
            
            if (Collection.class.isAssignableFrom(o.getClass())) {
                LinkedList<Object> list = (LinkedList) o;
                addJavaCollection(list, objectName);
            } else {
                if (o.getClass().getFields().length > 0)  {
                    store.put(
                            newId, new StringObject(objectName,newId,"{}" ));
                }
                
            }
        }
    }
    
    @Override
    public void addJavaCollection(Collection o, String collectionName) {
        DBID newId = (DBID)generateUniqueOID();
        ComplexObject cplxObj = new ComplexObject(collectionName, newId, null);
        //System.out.println("putting new complex " + collectionName);
        for ( Object obj : o) {
            for ( Field f : obj.getClass().getFields() ) {
                try {
                    addJavaObject(f.get(obj), f.getName());
                    } 
                    catch (IllegalArgumentException e) { e.printStackTrace(); } 
                    catch (IllegalAccessException e) { e.printStackTrace(); }
            }
        }
        store.put(newId, cplxObj);
    }
    
    public void printDBState() {
        for (Entry<DBID, SBAObject> entry : store.entrySet()) {
            
            DBID i = (DBID)entry.getKey();
            SBAObject o = entry.getValue();
            String name = o.getName();
            
            if ( o instanceof SimpleObject ) {
                System.out.println("<i" + i.getOID() + ", " + name + ", " + o.getObj()+">");
            } else {
                String childStr = "";
                ComplexObject c = (ComplexObject) o;
                int tmp = 0;
                for ( IOID innerI : c.getChildElements() ) {
                    if (tmp > 0) { childStr += ", " ; }
                    childStr += "i" + ((DBID)innerI).getOID();
                    ++tmp;
                }
                System.out.println("<i" + i.getOID() + ", " + name + ", {" + childStr + "}>");
            }
            
            
        }
        
    }
    

}
